﻿// -----------------------------------------------------------------------
// <copyright file="SmallIntColumn.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System.Data.Odbc;

namespace Centido.Core
{
    using System;


    /// <summary>
    ///   A small integer table column.
    /// </summary>
    /// <remarks>A small integer table column can store values ranging from -32768 to 32767.</remarks>
    /// <example>
    ///   The following example shows how to use a small integer column.
    /// <code>
    ///     using System;
    ///     using Centido.Core;
    /// 
    ///     class Sample
    ///     {
    ///         static void Main()
    ///         {
    ///             var domainConfig = new DomainConfiguration()
    ///             {
    ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
    ///                 DbProviderName = "MSSQLSERVER"
    ///             };
    ///         
    ///             var domain = Domain.Build(domainConfig);
    ///         
    ///             domain.DeleteAll();
    ///             
    ///             //Creating a table
    ///             var table = domain.AddTable("my_table");
    ///             table.GrantAll();
    /// 
    ///             //Creating a small integer column
    ///             var column = table.AddColumn("my_column", ColumnType.SmallInteger)
    ///                 as SmallIntColumn;
    /// 
    ///             //Setting the column bounds
    ///             column.Minimum = 0;
    ///             column.Maximum = 10;
    /// 
    ///             //Setting the column default value
    ///             column.DefaultValue = 1;
    /// 
    ///             //Inserting a new row
    ///             var id = table.InsertRow();
    /// 
    ///             Console.WriteLine("The inserted value = {0}", column.Read(id).GetSmallInteger());
    ///         }
    ///     }
    /// </code>
    /// </example>
    public sealed class SmallIntColumn : Column, IDefaultable<short?>, IBoundable<short?>
    {
        /// <summary>
        ///   The default value of the column.
        /// </summary>
        private short? _defaultValue;

        /// <summary>
        ///   The minimum value of the column.
        /// </summary>
        private short? _minimum;

        /// <summary>
        ///   The maximum value of the column.
        /// </summary>
        private short? _maximum;

        /// <summary>
        ///   Initializes a new instance of the <see cref = "Centido.Core.SmallIntColumn" /> class
        ///   for the specified column id and parent table.
        /// </summary>
        /// <param name = "columnId">The column id.</param>
        /// <param name = "table">The parent table.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        internal SmallIntColumn(Guid columnId, Table table)
            : base(columnId, table)
        {
        }

        /// <summary>
        ///   Initializes a new instance of the <see cref = "Centido.Core.SmallIntColumn" /> class
        ///   for the specified column id, name, display name,
        /// description, nullability, order index, customization level and parent table.
        /// </summary>
        /// <param name="columnId">The column id.</param>
        /// <param name="table">The parent table.</param>
        /// <param name="columnName">The column name.</param>
        /// <param name="displayName">The column display name.</param>
        /// <param name="description">The column description.</param>
        /// <param name="isNotNull">The column ability to hold null values.</param>
        /// <param name="orderIndex">The column order index.</param>
        /// <param name="customizationlevel">The column customization level.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="columnName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="columnName"/> is an empty string.</exception>
        internal SmallIntColumn(Guid columnId, Table table,
            string columnName, string displayName,
            string description, bool isNotNull,
            int orderIndex, int customizationlevel) : base(columnId, table,
            columnName, displayName, description, isNotNull, orderIndex, customizationlevel)
        {
        }

        /// <summary>
        ///   Gets the column type.
        /// </summary>
        public override ColumnType ColumnType
        {
            get { return ColumnType.SmallInteger; }
        }

        /// <summary>
        ///   Gets the column value database type.
        /// </summary>
        public override OdbcType ValueOdbcType
        {
            get { return OdbcType.SmallInt; }
        }

        /// <summary>
        ///   Gets the column backend table name.
        /// </summary>
        protected override string BackEndTableName
        {
            get { return Domain.GetObjectFullName(Db.Tables.SmallIntegerColumnList.Name); }
        }

        /// <summary>
        ///   Gets or sets the maximum value of the column.
        /// </summary>
        /// <remarks>The current user has to have the permission to change the schema in order to set this property.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the the current user has no permission to set this property.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to set this property to true and there are rows that contain invalid values.</exception>
        /// <example>
        ///   The following example shows how to set the maximum value of a small integer column.
        /// <code>
        ///     using System;
        ///     using System.Data.Common;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");
        ///             table.GrantAll();
        /// 
        ///             //Creating a small integer column
        ///             var column = table.AddColumn("my_column", ColumnType.SmallInteger)
        ///                 as SmallIntColumn;
        /// 
        ///             //Setting the column maximum
        ///             column.Maximum = 1;
        /// 
        ///             //Trying to insert an invalid value
        ///             try
        ///             {
        ///                 table.InsertRow(column.Value(2));
        ///             }
        ///             catch(DbException)
        ///             {
        ///                 Console.WriteLine("Inserting values greater than the maximum is forbidden");
        ///             }
        /// 
        ///             Console.WriteLine("Table: row count = {0}", table.RowCount);
        /// 
        ///             //Inserting a valid value
        ///             table.InsertRow(column.Value(0));
        /// 
        ///             Console.WriteLine("Table: row count = {0}", table.RowCount);
        ///         }
        ///     } 
        /// </code>
        /// </example>
        public short? Maximum
        {
            get { return _maximum ?? (_maximum = DbValue.GetSmallInteger(
                ReadProperty(Db.Tables.SmallIntegerColumnList.Col.Maximum))); }
            set
            {
                if (_maximum.HasValue && _maximum == value) return;

                Domain.DbAccess.ExecuteNonQuery(
                    cmd =>
                    {
                        cmd.CommandText = string.Format("{{call {0}(?,?)}}",
                            Domain.GetObjectFullName(Db.Procedures.SetSmallIntMaximum));

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_column_id",
                            OdbcType.Binary, GetRawId()));
                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_maximum",
                            OdbcType.Decimal, DbValue.GetDbValue(value)));
                    });

                _maximum = value;
            }
        }

        /// <summary>
        ///   Gets or sets the minimum value of the column.
        /// </summary>
        /// <remarks>The current user has to have the permission to change the schema in order to set this property.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to set this property.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to set this property to true and there are rows that contain invalid values.</exception>
        /// <example>
        ///   The following example shows how to set the minimum value of a small integer column.
        /// <code>
        ///     using System;
        ///     using System.Data.Common;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");
        ///             table.GrantAll();
        /// 
        ///             //Creating an small integer column
        ///             var column = table.AddColumn("my_column", ColumnType.SmallInteger)
        ///                 as SmallIntColumn;
        /// 
        ///             //Setting the column minimum
        ///             column.Minimum = 1;
        /// 
        ///             //Trying to insert an invalid value
        ///             try
        ///             {
        ///                 table.InsertRow(column.Value(0));
        ///             }
        ///             catch(DbException)
        ///             {
        ///                 Console.WriteLine("Inserting values less than the minimum is forbidden");
        ///             }
        /// 
        ///             Console.WriteLine("Table: row count = {0}", table.RowCount);
        /// 
        ///             //Inserting a valid value
        ///             table.InsertRow(column.Value(2));
        /// 
        ///             Console.WriteLine("Table: row count = {0}", table.RowCount);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public short? Minimum
        {
            get { return _minimum ?? (_minimum = DbValue.GetSmallInteger(
                ReadProperty(Db.Tables.SmallIntegerColumnList.Col.Minimum))); }
            set
            {
                if (_minimum.HasValue && _minimum == value) return;

                Domain.DbAccess.ExecuteNonQuery(
                    cmd =>
                    {
                        cmd.CommandText = string.Format("{{call {0}(?,?)}}",
                            Domain.GetObjectFullName(Db.Procedures.SetSmallIntMinimum));

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_column_id",
                            OdbcType.Binary, GetRawId()));
                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_minimum",
                            OdbcType.Decimal, DbValue.GetDbValue(value)));
                    });

                _minimum = value;
            }
        }


        /// <summary>
        ///   Gets or sets the default value of the column.
        /// </summary>
        /// <remarks>The current user has to have the permission to change the schema in order to set this property.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to set this property.</exception>
        /// <example>
        ///   The following example shows how to set the default value of a small integer column.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");
        ///             table.GrantAll();
        /// 
        ///             //Creating a small integer column
        ///             var column = table.AddColumn("my_column", ColumnType.SmallInteger)
        ///                 as SmallIntColumn;
        /// 
        ///             //Setting the column default value
        ///             column.DefaultValue = 1;
        /// 
        ///             //Inserting a row
        ///             var id = table.InsertRow();
        /// 
        ///             Console.WriteLine("Inserted value = {0}", column.Read(id).GetSmallInteger());
        ///         }
        ///     }
        /// </code>
        /// </example>
        public short? DefaultValue
        {
            get { return _defaultValue ?? (_defaultValue = DbValue.GetSmallInteger(
                ReadProperty(Db.Tables.SmallIntegerColumnList.Col.DefaultValue))); }
            set
            {
                if (_defaultValue.HasValue && _defaultValue == value) return;

                Domain.DbAccess.ExecuteNonQuery(
                    cmd =>
                    {
                        cmd.CommandText = string.Format("{{call {0}(?,?)}}",
                            Domain.GetObjectFullName(Db.Procedures.SetSmallIntDefault));

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_column_id",
                            OdbcType.Binary, GetRawId()));
                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_default_value",
                            OdbcType.Decimal, DbValue.GetDbValue(value)));
                    });

                _defaultValue = value;
            }
        }


        /// <summary>
        ///   Refreshes the properties of the column.
        /// </summary>
        public override void Refresh()
        {
            base.Refresh();

            _defaultValue = null;
            _maximum = null;
            _minimum = null;
        }

        /// <summary>
        ///   Adds a new small integer column with the specified name to the specified table.
        /// </summary>
        /// <remarks>The current user has to have the permission to change the schema in order to execute this function.</remarks>
        /// <param name="table">The table.</param>
        /// <param name="columnName">The column name.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        /// <exception cref="System.ArgumentNullException">Thrown when the value of <paramref name="columnName"/> is null.</exception>
        /// <exception cref="System.ArgumentException">Thrown when the value of <paramref name="columnName"/> is an empty string.</exception>
        /// <exception cref="System.ArgumentException">Thrown when the value of <paramref name="columnName"/> is not a valid identifier.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown the current user has no permission to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there is a column with the same name in the same user-defined table.</exception>
        /// <returns>Returns the newly created small integer table column.</returns>
        /// <example>
        ///   The following example shows how to add a small integer column.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating a small integer table column
        ///             var column = SmallIntColumn.Add(table, "my_column");
        ///         }
        ///     }
        /// </code>
        /// </example>
        public static SmallIntColumn Add(Table table, string columnName)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            return table.AddColumn(columnName, ColumnType.SmallInteger) as SmallIntColumn;
        }


        /// <summary>
        ///   Adds a new small integer table column with the specified id and name to the specified table.
        /// </summary>
        /// <remarks>The current user has to have the permission to change the schema in order to execute this function.</remarks>
        /// <param name="table">The table.</param>
        /// <param name="columnId">The column id.</param>
        /// <param name="columnName">The column name.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        /// <exception cref="System.ArgumentNullException">Thrown when the value of <paramref name="columnName"/> is null.</exception>
        /// <exception cref="System.ArgumentException">Thrown when the value of <paramref name="columnName"/> is an empty string.</exception>
        /// <exception cref="System.ArgumentException">Thrown when the value of <paramref name="columnName"/> is not a valid identifier.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown the current user has no permission to execute this function.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when there is a column with the same id or the same name in the same user-defined table.</exception>
        /// <returns>Returns the newly created small integer table column.</returns>
        /// <example>
        ///   The following example shows how to create a small integer table column.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating a small integer table column
        ///             var column = SmallIntColumn.Add(table, Guid.NewGuid(), "my_column");
        ///         }
        ///     }
        /// </code>
        /// </example>
        public static SmallIntColumn Add(Table table, Guid columnId, string columnName)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            return table.AddColumn(columnId, columnName, ColumnType.SmallInteger) as SmallIntColumn;
        }


        /// <summary>
        ///   Ensures that the table contains a small integer column with the specified name.
        /// </summary>
        /// <remarks>The current user has to have the permission to change the schema in order to execute this function.</remarks>
        /// <param name="table">The table.</param>
        /// <param name="columnName">The column name.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to add a column and
        /// the current user has no permission to change the schema.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="columnName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="columnName"/> is an empty string.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="columnName"/> is not a valid identifier.</exception>
        /// <exception cref="DbOperationException">Thrown when the table already has a column with the 
        /// specified name but different column type.</exception>
        /// <returns>Returns a small integer table column with the specified name.</returns>
        /// <example>
        ///   The following example shows how to ensure that a table has a small integer column with the specified name.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating a small integer table column
        ///             table.AddColumn("first_column", ColumnType.SmallInteger);
        /// 
        ///             //Ensuring the first column
        ///             SmallIntColumn.Ensure(table, "first_column");
        ///             //Ensuring the second column
        ///             SmallIntColumn.Ensure(table, "second_column");
        /// 
        ///             foreach(var column in table.Columns)
        ///                 Console.WriteLine("Table column: name = {0}, type = {1}", column.Name, column.ColumnType);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public static SmallIntColumn Ensure(Table table, string columnName)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            return table.EnsureColumn(columnName, ColumnType.SmallInteger) as SmallIntColumn;
        }


        /// <summary>
        ///   Ensures that the table contains a small integer column with the specified id and the specified name.
        /// </summary>
        /// <param name="table">The table.</param>
        /// <param name="columnId">The column id.</param>
        /// <param name="columnName">The column name.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when trying to add a column and
        /// the current user has no permission to change the schema.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="columnName"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="columnName"/> is an empty string.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="columnName"/> is not a valid identifier.</exception>
        /// <exception cref="DbOperationException">Thrown when the table already has a column with the
        /// specified id but different column name.</exception>
        /// <exception cref="DbOperationException">Thrown when the table already has a column with the 
        /// specified id but different column type.</exception>
        /// <exception cref="DbOperationException">Thrown when the table already has a column with the specified name
        /// but different column id.</exception>
        /// <returns>Returns the table column with the specified id, the specified name and the specified type.</returns>
        /// <returns>Returns the table column with the specified id, the specified name and the specified type.</returns>
        /// <example>
        ///   The following example shows how to ensure a small integer column with the specified id and
        /// the specified name.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///             
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a new table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Creating a table column
        ///             var columnId = Guid.NewGuid();
        ///             table.AddColumn(columnId, "first_column", ColumnType.SmallInteger);
        /// 
        ///             //Ensuring the first column
        ///             SmallIntColumn.Ensure(table, columnId, "first_column");
        ///             //Ensuring the second column
        ///             SmallIntColumn.Ensure(table, Guid.NewGuid(), "second_column");
        /// 
        ///             foreach(var column in table.Columns)
        ///                 Console.WriteLine("Table column: name = {0}, type = {1}", column.Name, column.ColumnType);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public static SmallIntColumn Ensure(Table table, Guid columnId, string columnName)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            return table.EnsureColumn(columnId, columnName, ColumnType.SmallInteger) as SmallIntColumn;
        }
    }
}